package Sampling;

import java.util.ArrayList;
import java.util.Random;

import Definitions.GraphClass;
import Definitions.NodeClass;
import Global.ConstantVariable;
import Global.GlobalClass;
/*****************************************************************
* Name : SnowballSampling_2
* Aim  : This class aims to apply Snowbal sampling algorithm start with a random node
* define test val and train sets
*****************************************************************/
public class SnowballSamplingClass extends SamplingAbstractClass{

	private int sizeOfGraph;	
	/*varyans*/
	private double threshold;
	private int edgeType;
	private int degree;
	private double needed;
	private int foldNumber;
	ArrayList<Integer> numberOfNodeListedAsClassOrderCurrentlySelected; 
	ArrayList <ArrayList<Integer>> listOfCanBeSelectNodeSeperatedAsClases;
	private double unusedRatio;
	
	Random random;
	
	/**
	 *  0 - 0 0.95 default edgetype degree..
	 * @param g
	 * @param global
	 * @param ratios
	 */
	public SnowballSamplingClass(GraphClass g, GlobalClass global, double... ratios) {
		this(g, global, ratios[0], ratios[1], ratios[2], ratios[3], 0, 0, 0.95);
		
		this.random = new Random();
	}
	
	public SnowballSamplingClass(GraphClass g, GlobalClass global, double testRatio, double valRatio, double unusedRatio, double trainRatio) {
		this(g, global, testRatio, valRatio, unusedRatio, trainRatio, 0, 0.95);
		this.random = new Random();
	}
	
	
	/*****************************************************************
	* Function Name:	SnowballSampling_2
	* Aim:				set the parametres of SS 
	*    				to apply the Snowball sampling algorithm and seperate data test/train/val 				
	* Inputs:		 	double testRatio   request testRatios of data
	* 					double valRatio    request validationRatio of data 
	* 					int edgeType      which link information will be used 		
	* 
	* Outputs:			*** 			
	* Data Structures: 	***
	* Algorithms: 	
	* @throws Exception ***
	*****************************************************************/
	
	public SnowballSamplingClass(GraphClass g, GlobalClass global, double testRatio, double valRatio, double unusedRatio, double trainRatio, int edgeType, int degree, double threshold) 
	{		

		super(g, global, testRatio, valRatio, unusedRatio, trainRatio);

		this.random = new Random();
		
		try
		{
			
			if((this.testRatio<1 &&  this.testRatio>0  && this.valRatio+ this.testRatio<1))
			{	
				this.name = ConstantVariable.Sampling_Constants.SNOWBALL_SAMPLING_METHOD;
				/*threshold can be change*/
				this.threshold= threshold;				
				this.edgeType= edgeType;
				this.degree = degree;
				
				numberOfNodeListedAsClassOrderCurrentlySelected= new ArrayList<Integer> ();
				listOfCanBeSelectNodeSeperatedAsClases= new ArrayList <ArrayList<Integer>> ();
				
				for(int i=0 ; i< global.classList.size(); i++)
				{
					ArrayList<Integer> temp= new ArrayList<Integer>();
					listOfCanBeSelectNodeSeperatedAsClases.add(temp);
				}
				
			}
			else {
				throw new SamplingException("Wrong  Parametre is given to SS");
			}
		}
		
		catch(SamplingException e) { 
			System.out.println("Exeption occured:  "+e.getMessage()  );
			e.printStackTrace();
		}
	}
		
	public void generateSampling(GraphClass g, int fold) 
	{
		initializeTestTrainValidationAssignmentsForTheFold();
		
		this.foldNumber =fold;
		
		numberOfNodeListedAsClassOrderCurrentlySelected.clear();
		listOfCanBeSelectNodeSeperatedAsClases.clear();
		
		for(int i=0 ; i< getGlobal().classList.size(); i++)
		{
			ArrayList<Integer> temp= new ArrayList<Integer>();
			listOfCanBeSelectNodeSeperatedAsClases.add(temp);
		}
		generateSampling(g);
		setClassRatiosWithTrain(g);
	}
	
	
	/*****************************************************************
	* Function Name:	SnowballSampling_2
	* Aim:				to apply the Snowball sampling algorithm and seperate data test/train/val 
	* 					speration information is keep inside class must be set using Constructor
	*  				
	* Inputs:		 	Graph g 		
	* Outputs:			*** 			
	* Data Structures: 	***
	* Algorithms: 	
	* 
	* First test than validation set is define after train set is assign.
	* While test and train assign a priorList which keep connected neighbours as class. For this list modes are seleted
	* connected to each orther and check this nodes ratios to provide the same structure with the original graph.	
	 * @throws Exception ***
	*****************************************************************/
	public void generateSampling(GraphClass g) 
	{
		this.sizeOfGraph  = g.getDataSet().size();
		int totalNumberSelected=0;
		int selectedNode=-1;
		/* defines the class order which is needed for normalizing the sampling set*/
		int priorClassOrder ;
		/*threshold can be change*/
		double neededVal   = (this.valRatio  * this.sizeOfGraph);
		double neededTest  = (this.testRatio * this.sizeOfGraph);

		int neededTrain = (int)((float)this.sizeOfGraph * this.trainRatio);
		this.needed= neededTest+neededTrain;
		
		if(needed>sizeOfGraph)
		{
			needed = sizeOfGraph;
		}
		
		boolean isFinishTest = false;
		boolean isFinishVal  = false;

	//	Random random= new Random ();
		this.random.setSeed(foldNumber+100);
		/* Initialize the selected number of nodes */
		for(int i= 0; i< getGlobal().classList.size() ;i++)
		{
			numberOfNodeListedAsClassOrderCurrentlySelected.add(i, 0);	
		}

		/* Define the initialization node */
		selectedNode= random.nextInt(this.sizeOfGraph);			
		/*add Node to test set*/
		if(ConstantVariable.ExecutionParameters.snowballSamplingBugEnabled)
			addNodeToTestSet(g.getDataSet().get(selectedNode));
		else
			proceduresToBeDoneToAddTheNodeToTestSetInSnowballSampling(g.getDataSet().get(selectedNode));
		/*Update the numberOf Selected for test*/
		totalNumberSelected++;

		/*until chosing test set complete */
		while( (!isFinishTest) ) 
		{
			boolean isDefine=false;
			while(!isDefine)
			{

				/*creating test set is prior if test is not finish chose */
				priorClassOrder = definePriorClass(numberOfNodeListedAsClassOrderCurrentlySelected, totalNumberSelected,g);
				/*chose node for adding to test*/


				int sizeOfPriorClass= listOfCanBeSelectNodeSeperatedAsClases.get(priorClassOrder).size();

				int order= random.nextInt() % sizeOfPriorClass;
				
				selectedNode= listOfCanBeSelectNodeSeperatedAsClases.get(priorClassOrder).get(order);

				listOfCanBeSelectNodeSeperatedAsClases.get(priorClassOrder).remove(order);
				if(isTheAssignmentOfTheNodeForThisFoldDone(g.getDataSet().get(selectedNode)))
				{
					if(totalNumberSelected < neededTest)
					{
						/*node define as test */
						if(ConstantVariable.ExecutionParameters.snowballSamplingBugEnabled)
							addNodeToTestSet(g.getDataSet().get(selectedNode));
						else
							proceduresToBeDoneToAddTheNodeToTestSetInSnowballSampling(g.getDataSet().get(selectedNode));
						totalNumberSelected++;
					}
					else// if(sizeOfGraph != needed)
					{
						if(ConstantVariable.ExecutionParameters.snowballSamplingBugEnabled)
							addNodeToTrainingSet(g.getDataSet().get(selectedNode));
						else
							proceduresToBeDoneToAddTheNodeToTestSetInSnowballSampling(g.getDataSet().get(selectedNode), 0);
						totalNumberSelected++;
						
					} 
					/*else {
		
						for(NodeClass node : g.getDataSet())
						{
							if(this.isTheAssignmentOfTheNodeForThisFoldDone(node))
							{
								addNodeToTrainingSet(node);
							}
						}
						isFinishTest=true;
					}

					*/
					isDefine=true;
					
				}

			}
			/*total number of selected is access the needed it must be break ->isFinishTest will be true*/
			if(totalNumberSelected>= needed)
			{
				isFinishTest=true;
			}
		}	
		/*initialize the selectednode */
		totalNumberSelected=0;
		this.needed= neededVal;

		/*untill chosing validation set complete */
		int t=0;

		while(this.needed >0) 
		{
			int m= random.nextInt(sizeOfGraph);
			if(isTheAssignmentOfTheNodeForThisFoldDone(g.getDataSet().get(m)))
			{
				if(ConstantVariable.ExecutionParameters.snowballSamplingBugEnabled)
					addNodeToValidationSet(g.getDataSet().get(m));
				else
					proceduresToBeDoneToAddTheNodeToValidationSetInSnowballSampling(g.getDataSet().get(m));
				t++;
				this.needed --;
			}

		}
	
		
		for(NodeClass node : g.getDataSet())
		{
			if(this.isTheAssignmentOfTheNodeForThisFoldDone(node))
			{
				addNodeToUnusedSet(node);
			}
		}
		
		System.out.println("Train: " + this.getTrainNodes().size() + "  Test: " + this.getTestNodes().size()+ " Test: " + g.getDataSet().size() );
		System.out.println("Unused: " + this.getUnusedNodes().size());		
		System.out.println("Unused: " + this.getUnusedNodes().size());
		
	}


	/*****************************************************************
	* Function Name:	addNodeToValSet
	* Aim:				add the specified node to Validation Set
	* 					neighbours are add to can be select list	 
	* Inputs:		 	Node selected
	* Outputs:			void * 					 		
	* Data Structures: 	***
	* Algorithms: 		***
	*****************************************************************/	
	public void proceduresToBeDoneToAddTheNodeToValidationSetInSnowballSampling(NodeClass selectedNode)
	{
		addNodeToValidationSet(selectedNode);		
		/*Update the numberOf Selected for test*/		
		int currentlySelectedClassOrder= selectedNode.getClassOrder();
		int temp= numberOfNodeListedAsClassOrderCurrentlySelected.get(currentlySelectedClassOrder);
		numberOfNodeListedAsClassOrderCurrentlySelected.set(currentlySelectedClassOrder, temp+1);
		/*Add neighbour to can be selected Lists*/
		
		addNeighbour(selectedNode);
	}

	/*****************************************************************
	* Function Name:	addNodeToTestSet
	* Aim:				add the specified node to Test Set
	* 					neighbours are add to can be select list	 
	* Inputs:		 	Node selected
	* Outputs:			void * 					 		
	* Data Structures: 	***
	* Algorithms: 		***
	*****************************************************************/
	public void proceduresToBeDoneToAddTheNodeToTestSetInSnowballSampling(NodeClass selectedNode, int i)
	{
		addNodeToTrainingSet(selectedNode);		
		/*Update the numberOf Selected for test*/		
		int currentlySelectedClassOrder= selectedNode.getClassOrder();
		int temp= numberOfNodeListedAsClassOrderCurrentlySelected.get(currentlySelectedClassOrder);
		numberOfNodeListedAsClassOrderCurrentlySelected.set(currentlySelectedClassOrder, temp+1);
		/*Add neighbour to can be selected Lists*/
		addNeighbour(selectedNode);
	}

	
	/*****************************************************************
	* Function Name:	addNodeToTestSet
	* Aim:				add the specified node to Test Set
	* 					neighbours are add to can be select list	 
	* Inputs:		 	Node selected
	* Outputs:			void * 					 		
	* Data Structures: 	***
	* Algorithms: 		***
	*****************************************************************/
	public void proceduresToBeDoneToAddTheNodeToTestSetInSnowballSampling(NodeClass selectedNode)
	{
		addNodeToTestSet(selectedNode);		
		/*Update the numberOf Selected for test*/		
		int currentlySelectedClassOrder= selectedNode.getClassOrder();
		int temp= numberOfNodeListedAsClassOrderCurrentlySelected.get(currentlySelectedClassOrder);
		numberOfNodeListedAsClassOrderCurrentlySelected.set(currentlySelectedClassOrder, temp+1);
		/*Add neighbour to can be selected Lists*/
		addNeighbour(selectedNode);
	}

	/*****************************************************************
	* Function Name:	addNeighbour
	* Aim:				check the define node is added list before or not 
	* Inputs:		 	Node selected
	* Outputs:			void * 					 		
	* Data Structures: 	***
	* Algorithms: 		***
	*****************************************************************/
	public void addNeighbour(NodeClass selected)
	{
		int order;
		NodeClass tempNode;
		for(int i=0; i<selected.getNeighbours(edgeType,degree).size();i++)
		{
			tempNode= selected.getNeighbours(edgeType,degree).get(i);
			/*update the list using class information*/
			/*all connected nodes(neighbours can be select so add the list)*/
			order =selected.getNeighbours(edgeType,degree).get(i).getClassOrder();
			
			if(isTheAssignmentOfTheNodeForThisFoldDone(tempNode) && !containsOrderInList(order ,tempNode.getOrder()))
			{
				listOfCanBeSelectNodeSeperatedAsClases.get(order).add(tempNode.getOrder());
			}
		}
		
	}
	
	/**
	 * @throws Exception ***************************************************************
	* Function Name:	definePriorClass
	* Aim:				to understand which class is needed more 
	* Inputs:		 	ArrayList<Integer> selected shows how many node is chosen as the class
	* 					
	* Outputs:			prior classOrder 			
	* Data Structures: 	***
	* Algorithms: 		***
	* @throws
	*****************************************************************/
	
	public int definePriorClass ( ArrayList<Integer> selected, int numberOfSelected, GraphClass g) //throws Exception
	{
	/*	try
		{ */
		/**to find the prior  class which is chose less as on orginal structure ratios***/
			double ratios[]= new double[getGlobal().classList.size()]; 
			int minumumSelectMaksimumNeededClassOrder=Integer.MIN_VALUE;
			double minRatio=Double.MAX_VALUE; 
		
			//*chose the prior class if threshold is not pass otherwise chose randomly*// 
			double chosenNumber= (double)(needed*threshold);
			
			if(chosenNumber<numberOfSelected)
			{
				//if threshold is pass it is nor neccessary compare the ratios, randomly chose needed class
				Random random= new Random ();
				random.setSeed(foldNumber+100);
				int m= random.nextInt(getGlobal().classList.size());
				int start=0;
				boolean define=false; 
				while(!define && m<getGlobal().classList.size() )
				{
					if(listOfCanBeSelectNodeSeperatedAsClases.get(m).size()!= 0)
					{	
						define= true;
						minumumSelectMaksimumNeededClassOrder=m;
						break;
					}
					else
					{
						start++;
						m++;	
					}
				}
			}
			else
			{
			    /* otherwise needed ratio is define after compare the ratios*/
				for(int i=0; i<listOfCanBeSelectNodeSeperatedAsClases.size(); i++)
				{
					if(listOfCanBeSelectNodeSeperatedAsClases.get(i).size()!= 0)
					{
						
						double ratioOfClass = getGlobal().classList.get(i).getRatio();
						double currentRatios=selected.get(i)/ (double)numberOfSelected;
						ratios[i]= (currentRatios/ ratioOfClass);
						if(minRatio> ratios[i])
						{
							minumumSelectMaksimumNeededClassOrder=i;
							minRatio=ratios[i];
						}
					}
				}
			}
			
			if(minumumSelectMaksimumNeededClassOrder<0)
			{
				/*if wrong assignment is make*/
				int counter=0;
				//Random random= new Random ();
				//random.setSeed(foldNumber+100);
				int m= random.nextInt(sizeOfGraph);

				boolean chose=false;
				while(!chose && counter<sizeOfGraph)
				{
					if(isTheAssignmentOfTheNodeForThisFoldDone(g.getDataSet().get(m)))					
					{
						try
						{
						/*node is check if previously set remove the list*/
						int currentlySelectedClassOrder= g.getDataSet().get(m).getClassOrder();
						int temp= numberOfNodeListedAsClassOrderCurrentlySelected.get(currentlySelectedClassOrder);
						numberOfNodeListedAsClassOrderCurrentlySelected.set(currentlySelectedClassOrder, temp+1);
						listOfCanBeSelectNodeSeperatedAsClases.get(currentlySelectedClassOrder).add(m);
						minumumSelectMaksimumNeededClassOrder= currentlySelectedClassOrder;
						chose=true;
						}catch (Exception e) {
							// TODO: handle exception
							e.printStackTrace();
							System.exit(0);
						}
					}
					else
					{
						m= (m+1)%sizeOfGraph;
						counter++;
						
					}
				}
			}
			return minumumSelectMaksimumNeededClassOrder;		
	}
	
	/*****************************************************************
	* Function Name:	containsOrderInList
	* Aim:				check the define node is added list before or not 
	* Inputs:		 	int classOrder define node's class order
	* 					int variable define node's order in the graph
	* Outputs:			boolean true if defined node is in list listOfCanBeSelectedAsClasses
	* 					 		false otherwise	
	* Data Structures: 	***
	* Algorithms: 		***
	*****************************************************************/
	public boolean containsOrderInList(int classOrder,int variable)
	{
		for(int i=0; i<listOfCanBeSelectNodeSeperatedAsClases.get(classOrder).size();i++)
		{
			if( listOfCanBeSelectNodeSeperatedAsClases.get(classOrder).get(i)==variable)
				return true;
		}
		return false;
	}

}
